package be.kdg.bombermanunlimited.client.network.packet;

import java.util.Arrays;

/**
 * Een packet waarin grote delen al zijn ingevuld.
 * 
 * Deze klasse gedraagd zich ook deels als een factory. Beter zou zijn dat we
 * deze functionaliteit splitsen in een echte factory en zo nog meer
 * modulariteit kunnen voorzien. Dan zou dit ook bruikbaar zijn in andere
 * projecten.
 * 
 * @author Philip
 * 
 */
public abstract class Packet implements IPacket {
	// De types die gebruikt worden

	/*
	 * public static final byte TYPE_GAME_DATA = 0; public static final byte
	 * TYPE_MEDIA_DATA = 1;
	 */
	public static final byte TYPE_CHAT_DATA = 2;
	public static final byte TYPE_AUDIO_INFO = 3;
	public static final byte TYPE_AUDIO_DATA = 4;
	public static final byte TYPE_CONTROL_DATA = 5;

	// De grote van een basis packet
	private static final int HEADER_SIZE = 5;

	/**
	 * the actual data, without type and length
	 */
	protected PacketData data;

	/**
	 * Maak een leeg packet
	 */
	protected Packet() {
		this.data = new PacketData();
	}

	/**
	 * Maak een packet aan met gegeven data
	 * 
	 * @param data
	 *            de data, ruw (inclusief header) of zonder header
	 * @param removeHeader
	 *            true als de Packet header eraf geknipt moet worden, anders
	 *            false
	 */
	protected Packet(byte[] data, boolean removeHeader) {
		this(new PacketData(data), removeHeader);
	}

	/**
	 * Idem als hierboven
	 * 
	 * @param data
	 *            idem als hierboven, enkel al in een PacketData object gegoten
	 * @param removeHeader
	 *            idem als hierboven
	 */
	protected Packet(PacketData data, boolean removeHeader) {
		this.data = data;
		if (removeHeader) {
			// haal het type packet eraf
			this.data.popByte();
			// haal de lengte van het packet eraf
			int len = this.data.popInt();
			if (this.data.getDataLength() > len) {
				// als de data in het object langer is dan len, knip het
				// overtollige eraf
				this.data.keepData(0, len);
			}
		}
	}

	/**
	 * statische functie om te controlleren of de ruwe data voldoende data bevat
	 * om een packet op te bouwen
	 * 
	 * @param data
	 *            De ruwe data
	 * @return true als er voldoende data is
	 */
	public static boolean isDataComplete(byte[] data) {
		if (data.length > Packet.HEADER_SIZE) {
			// int len = (int) ((data[1] << 24) | (data[2] << 16) | (data[3] <<
			// 8) | data[4]);
			int len = calcLenFromRawData(data);

			return (data.length >= len + Packet.HEADER_SIZE);
		} else {
			return false;
		}
	}

	public int getTotalHeaderLength() {
		return Packet.HEADER_SIZE;
	}

	/**
	 * Functie die een packet maakt uit ruwe data
	 * 
	 * @param data
	 *            de ruwe data
	 * @return Een packet, type is afhankelijk van het type veld in de ruwe data
	 */
	public static Packet createPacket(byte[] data) {
		int len = calcLenFromRawData(data) + Packet.HEADER_SIZE;
		// het type packet uitlezen
		byte type = data[0];

		// get the real packet part and ignore the rest of the next packet
		// this is better for performance reasons, otherwise the constructor
		// works with to big packets and they do a lot of copy
		PacketData tmp = new PacketData(Arrays.copyOfRange(data, 0, len));

		// maak een packet van het correcte type
		switch (type) {
		case Packet.TYPE_AUDIO_DATA:
			return new AudioDataPacket(tmp);
		case Packet.TYPE_AUDIO_INFO:
			return new AudioInfoPacket(tmp);
		case Packet.TYPE_CHAT_DATA:
			return new ChatPacket(tmp);
			/*
			 * case Packet.TYPE_GAME_DATA: return new Packet(tmp); case
			 * Packet.TYPE_MEDIA_DATA: return new Packet(tmp);
			 */
		case Packet.TYPE_CONTROL_DATA:
			return new ControlPacket(tmp);
		default:
			return null;
		}
	}

	/**
	 * Maak een basis vorm van ruwe data. Voor vele packeten is deze werkwijze
	 * voldoende (ChatPacket, ControlPacket...). Voor enkele niet:
	 * AudioDataPacket, deze zal deze functie dan ook zelf overschrijven.
	 * PaketData wordt gebruiktomdat deze handige functies heeft om data toe te
	 * voegen in een array. LET OP: deze functie beschadigd het packet. Het
	 * packet zelf is na deze functie niet meer bruikbaar!
	 * 
	 * @param data
	 *            De data die verzonden moet worden (zonder header)
	 * @return de byte array
	 */
	protected byte[] convertToByteArray(PacketData data) {
		data.pushFront(data.getDataLength());
		data.pushFront(getType());

		return data.getData();
	}

	/**
	 * dummy functie die het aanroepen vergemakkelijkt.
	 */
	public byte[] convertToByteArray() {
		return convertToByteArray(new PacketData(data.getData()));
	}

	/**
	 * data lengte, exclusief header lengte
	 */
	public int getDataLength() {
		return data.getDataLength();
	}

	/**
	 * data zonder header
	 */
	public byte[] getData() {
		return data.getData();
	}

	abstract public byte getType();

	public String getTypeName() {
		switch (getType()) {
		case Packet.TYPE_AUDIO_DATA:
			return "TYPE_AUDIO_DATA";
		case Packet.TYPE_CHAT_DATA:
			return "TYPE_CHAT_DATA";
		case Packet.TYPE_AUDIO_INFO:
			return "TYPE_AUDIO_INFO";
		default:
			return "Unknown type";
		}
	}

	/**
	 * bepaal de lengte uit ruwe data.
	 * 
	 * @param data
	 * @return
	 */
	private static int calcLenFromRawData(byte[] data) {
		return (((int) data[1] << 24) & 0xFF000000)
				| (((int) data[2] << 16) & 0x00FF0000)
				| (((int) data[3] << 8) & 0x0000FF00)
				| ((int) data[4] & 0x000000FF);
	}
}
